
#include "common.h"
#include "hashtable.h"
#include "md5.h"
#include "cache_file.h"
#include "http.h"
#include "globals.h"
#include "cache_dir.h"
#include "cache_index.h"

DEFINE_HASHTABLE_INSERT(insert_some, cache_key, cache_file_t);
DEFINE_HASHTABLE_SEARCH(search_some, cache_key, cache_file_t);
DEFINE_HASHTABLE_REMOVE(remove_some, cache_key, cache_file_t);
DEFINE_HASHTABLE_ITERATOR_SEARCH(search_itr_some, cache_key);

static struct hashtable *cache_table; 

static int cache_key_compare(void *k1, void *k2);
static unsigned int cache_key_hash(void *k);
static int list_count(struct list_head *h);
static shm_t * shm_init(void);
static void mem_cache_free(mem_t *m);
static void shm_cache_free(shm_t *sm);

char *cache_statu_to_string(int statu)
{
	if(statu == CACHE_NONE)
		return "CACHE_NONE";
	else if(statu == CACHE_IN_DISK)
		return "CACHE_IN_DISK";
	else if(statu == CACHE_DOWNLOADING) 
		return "CACHE_DOWNLOADING";
	else if(statu == CACHE_DOWNLOAD_FAILED)
		return "CACHE_DOWNLOAD_FAILED";
	else 
		return "CACHE_STATU_UNKNOWN";
}

void cache_file_delete_and_free(cache_file_t *cache)
{
	if(!cache) 
		return;
	
	debug_log(3, "mark %s to be delete from cache table\n", cache->file->buf);
	cache_file_delete(cache);
	cache_file_free(cache);
}

int cache_file_insert(cache_file_t *cache)
{
	assert(cache);
	assert(cache->file);
	
	cache_key *key = (char *)strndup(cache->key, DIGEST_LENGTH);
	if(key == NULL) {
		error_log("key %s insert failed\n", key);
		return -1;
	}
	
	insert_some(cache_table, key, cache);
	debug_log(4, "%s insert\n", cache->file->buf);
	
	return 0;
}

cache_file_t *cache_file_delete(cache_file_t *cache)
{
	if(!cache) 
		return NULL;

	struct cache_dir *d;
	struct cache_index *ci;
	cache_key *key = cache->key;

	if(key == NULL) {
		warn_log("cache_file_delete %p, not have key\n", cache);
		return NULL;
	}
	remove_some(cache_table, key);
	return cache;
}

void cache_file_free(cache_file_t *cache)
{
	if(!cache) 
		return;

	int free = 0;
	
	if(cache->ref == 0) {
		free = 1;	
	}
	else 
		cache->should_free = 1;
	
	if(free) {
		debug_log(3, "cache %p free\n", cache);
		cache_index_add(cache, CACHE_INDEX_DEL);
		string_clean(cache->file);
		mem_cache_free(cache->mem_cache);
		shm_cache_free(cache->shm_cache);
		mempool_free(cache->human_key);
		mempool_free(cache);
	}
}

cache_file_t *cache_file_search(const char *url, const int size)
{
	assert(url);
	assert(cache_table);
		
	cache_file_t *cf = NULL;
	unsigned char *key = cache_key_public(url, size);

	if((cf = search_some(cache_table, (cache_key *)key))) {
		mempool_free(key);
		return  cf;
	}
	mempool_free(key);
	key = cache_key_private(url, size);
	if((cf = search_some(cache_table, (cache_key *)key))) {
		mempool_free(key);
		return cf;
	}
	else {
		mempool_free(key);
		debug_log(3, "'%.*s' not find\n", size, url);
		return NULL;
	}
}

cache_file_t *cache_file_search_from_key(const unsigned char *key)
{
	assert(key);
	assert(cache_table);
		
	cache_file_t *cf = NULL;

	if((cf = search_some(cache_table, (cache_key *)key))) {
		return  cf;
	}
	else 
		return NULL;
}

cache_file_t *cache_file_create(const char *url) 
{
	assert(url);
	
	char buf[MAX_BUFFER];
	cache_dir_t *dir;
	cache_file_t *cache = mempool_alloc(sizeof(*cache));
	if(NULL == cache) {
		error_log("%p\n", cache);
		return NULL;
	}

	/* init cache_file_t */
	strncpy(cache->key, cache_key_public(url, strlen(url)), DIGEST_LENGTH);
	cache->human_key = cache_key_ascii(cache->key, DIGEST_LENGTH);
	cache->dir_no = cache_dir_select();
	dir = conf->cache.dir->items[cache->dir_no];
	
	/* init cache file full path */
	cache->file = string_init(dir->path, strlen(dir->path));
	if(cache->file->buf[cache->file->offset - 1] != '/')
		string_append(cache->file, "/", 1);
	string_append(cache->file, "c-", 2);
	string_append(cache->file, cache->human_key, strlen(cache->human_key));
	string_append(cache->file, "\0", 1);
	cache->statu = CACHE_NONE;
	cache->should_free = 0;
	/* create mem cache and shm cache */
	cache->mem_cache = mem_init(conf->server.max_object_size_in_mem);
	cache->shm_cache = shm_init();
	debug_log(4, "%s created\n", cache->file->buf);
	return cache;
}

cache_file_t *cache_ref(cache_file_t *cache)
{
	assert(cache);
	int count; 

	cache->ref++;
	count = cache->ref;
	
	debug_log(3, "%s ref %d\n", cache->file->buf, count);
	return cache;
}

void cache_unref(cache_file_t *cache)
{
	if(!cache)
		return ;	
	
	int ref;
	
	cache->ref--;
	ref = cache->ref;
	
	debug_log(3, "%s ref %d, should free %d\n", cache->file->buf, ref, cache->should_free);
	if(ref == 0 && cache->should_free == 1)
		cache_file_delete_and_free(cache);
}

int cache_lock(void) 
{
	return pthread_mutex_lock(&cache_file_table_lock);
}

int cache_unlock(void)
{
	int ret = pthread_mutex_unlock(&cache_file_table_lock);
	assert(ret == 0) ;
	return 0;
}



static int list_count(struct list_head *h) 
{
	if(!h)
		return 0;

	struct list_head *l;
	int count = 0;

	list_for_each(l, h) 
		count++;

	return count;
}

/********************************************/
/********** cache table operates ************/

void cache_file_table_init(void)
{
	if(cache_table == NULL) 
		cache_table = create_hashtable(conf->server.objects, cache_key_hash, cache_key_compare);
	
	pthread_mutex_init(&cache_file_table_lock, NULL);

	debug_log(1, "size %d\n", cache_table->tablelength);
}

void cache_file_table_clean(void)
{
	if(cache_table)
		return ;
	hashtable_destroy(cache_table, 0);

	pthread_mutex_destroy(&cache_file_table_lock);
}

static int cache_key_compare(void *k1, void *k2) 
{
	return (0 == strcmp(k1, k2));
}
					
static unsigned int cache_key_hash(void *k)
{
	return hash_djb2(k);
}

/********************* end ******************/


mem_t * mem_init(const int size)
{
	if(size < 0) 
		return;

	mem_t *m = mempool_alloc(sizeof(*m));

	m->high = m->low = 0;
	m->inmem = string_limit_init(size);
	return m;
}

int mem_append(mem_t *mem, char *buf, int size) 
{
	if(!mem)
		return 0;

	string_append(mem->inmem, buf, size);
	mem->high += size;
	debug_log(6, "append %d, now [%d, %d]\n", size, mem->low, mem->high);
	return size;
}

static shm_t * shm_init(void)
{
	shm_t *sm = mempool_alloc(sizeof(*sm));
	assert(sm);
	
	sm->low =  sm->high = 0;
	sm->fd = -1;

	return sm;
}

void mem_free(mem_t *m)
{
}

static void mem_cache_free(mem_t *m)
{
}

static void shm_cache_free(shm_t *sm)
{
}

void cache_file_reset(cache_file_t *cache)
{
	if(!cache)
		return;
	
	cache->header_size = cache->body_size = 0;
	cache->statu = CACHE_DOWNLOADING;
	mem_cache_free(cache->mem_cache);
	cache->mem_cache = NULL;
	shm_cache_free(cache->shm_cache);
	cache->shm_cache = NULL;
}

void cache_file_download_success(cache_file_t *cache, int header_size, int body_size)
{
	if(cache_lock()) 
		return;
	cache->statu = CACHE_IN_DISK;
	cache->header_size = header_size;
	cache->body_size = body_size;
	if(cache->should_free != 1)
		cache_index_add(cache, CACHE_INDEX_ADD);
	cache_unlock();
	debug_log(3, "%s download finish. H %d, B %d\n", cache->file->buf, cache->header_size, cache->body_size);
}

char *cache_able_statu_to_string(int statu)
{
	switch(statu) {
		case CACHE_ABLE_YES :
			return "CACHE_ABLE_YES";
		case CACHE_ABLE_NO :
			return "CACHE_ABLE_NO";
		default :
			return "CACHE_ABLE_UNKNOWN";
	}
}

int cache_able_string_to_statu(char *string)
{
	if(!string)
		return CACHE_ABLE_UNKNOWN;
	else if(strcasecmp(string, "cache_able_yes")) 
		return CACHE_ABLE_YES;
	else if(strcasecmp(string, "cache_able_no")) 
		return CACHE_ABLE_NO;
	else 
		return CACHE_ABLE_UNKNOWN;
}
